import child_process from 'child_process'
import path from 'path'
import * as recast from 'recast'
import fs from 'fs-extra'
import { parse as babelParse } from 'recast/parsers/babel'
import prettier from 'prettier'
import stringifyObject from 'stringify-object'
import { Parser } from './base'
// import i18n from '~/i18n'
import { Log } from '~/utils'
import { Config, Global } from '~/core'

const LanguageIds = {
  js: 'javascript',
  ts: 'typescript',
} as const

const LanguageExts = {
  js: 'm?js',
  ts: 'ts',
} as const

// 递归清洗对象，过滤空key、undefined/null值
function sanitizeObject(obj: any): any {
  if (Array.isArray(obj)) {
    return obj.map(sanitizeObject)
  }
  else if (obj && typeof obj === 'object') {
    const result: Record<string, any> = {}
    for (const [key, value] of Object.entries(obj)) {
      if (!key || typeof key !== 'string' || key.trim() === '') continue
      if (value === undefined || value === null)
        result[key] = ''
      else if (typeof value === 'object')
        result[key] = sanitizeObject(value)
      else
        result[key] = value
    }
    return result
  }
  return obj
}

// 用 stringify-object 生成 JS 对象字面量字符串
function objectToCode(obj: any): string {
  return stringifyObject(obj, {
    indent: '  ',
    singleQuotes: true,
  })
}

export class EcmascriptParser extends Parser {
  readonly readonly = false

  constructor(public readonly id: 'js'|'ts' = 'js') {
    super([LanguageIds[id]], LanguageExts[id])
  }

  async parse() {
    return {}
  }

  async dump() {
    return ''
  }

  async load(filepath: string) {
    const loader = path.resolve(Config.extensionPath!, 'assets/loader.js')
    const tsNode = Config.parsersTypescriptTsNodePath
    const dir = Global.rootpath
    const compilerOptions = {
      importHelpers: false,
      allowJs: true,
      module: 'commonjs',
      ...Config.parsersTypescriptCompilerOption,
    }
    const options = JSON.stringify(compilerOptions)
    const isWin = process.platform === 'win32'
    const quotedOptions = isWin
      ? `"${options.replace(/"/g, '\\"')}"`
      : `'${options}'`

    return new Promise<any>((resolve, reject) => {
      const args = [
        '--dir', dir,
        '--transpile-only',
        // '--compiler-options', `'${options}'`,
        '--compiler-options', quotedOptions,
        loader,
        filepath,
      ]

      Log.info(`[i18n-ally] spawn: ${tsNode} ${args.join(' ')}`)

      const process = child_process.spawn(tsNode, args, {
        shell: true,
        windowsHide: true,
      })

      let stdout = ''
      let stderr = ''

      process.stdout?.on('data', (data: Buffer) => {
        stdout += data.toString('utf8')
      })

      process.stderr?.on('data', (data: Buffer) => {
        stderr += data.toString('utf8')
      })

      process.on('close', (code: number) => {
        if (code !== 0) {
          const error = new Error(`Process exited with code ${code}\n${stderr}`)
          Log.error(error)
          reject(error)
          return
        }

        try {
          const result = JSON.parse(stdout)
          resolve(result)
        }
        catch (e) {
          Log.error(e)
          reject(e)
        }
      })

      process.on('error', (err: Error) => {
        Log.error(err)
        reject(err)
      })
    })
  }

  async save(filepath: string, object: object, blankLineBetweenObjects = false): Promise<void> {
    try {
      if (!await fs.pathExists(filepath))
        await fs.ensureFile(filepath)

      const code = await fs.readFile(filepath, 'utf-8')
      const ast = recast.parse(code, { parser: require('recast/parsers/typescript') })
      let found = false

      const sanitized = sanitizeObject(object)
      const codeStr = objectToCode(sanitized)

      recast.types.visit(ast, {
        visitExportDefaultDeclaration(path) {
          const exprAstRaw = babelParse(`(${codeStr})`).program.body[0]
          if (exprAstRaw.type === 'ExpressionStatement')
            path.node.declaration = exprAstRaw.expression as any
          else
            throw new Error('解析表达式失败，AST 不是 ExpressionStatement')
          found = true
          return false
        },
      })

      if (!found) {
        ast.program.body.push(
          recast.parse(`export default ${codeStr};`, { parser: require('recast/parsers/typescript') }).program.body[0],
        )
      }

      // 使用recast打印时设置quote: 'double'，让字符串用双引号
      const output = recast.print(ast, { quote: 'double' }).code
      let beautified = await prettier.format(output, { parser: 'babel', singleQuote: false, printWidth: 300 })

      if (blankLineBetweenObjects)
        beautified = beautified.replace(/},\n(\s+\w+: {)/g, '},\n\n$1')
      else
        beautified = beautified.replace(/},\n\s*\n(\s+\w+: {)/g, '},\n$1')

      await fs.writeFile(filepath, beautified, 'utf-8')
    }
    catch (e) {
      if (e instanceof Error)
        Log.error(e, true, 0)
      throw e
    }
  }
}
